package com.forg.mvc.core.common;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ClassUtil {

    private static final Logger logger = LoggerFactory.getLogger(ClassUtil.class);
    
    private static Map<String,List<Class<?>>> annotationClazzMap = new ConcurrentHashMap<>();
    
    private static final String BASE_PACKAGE = PropUtil.getString("base.package") ;
    
    private static final Boolean DEEP_SCAN = PropUtil.getBool("deep.scan",false);
    
    private static synchronized void initAnnotationClazzCache(){
        
        annotationClazzMap.clear();
        
        try {
            // 从包名获取 URL 类型的资源
            Enumeration<URL> urls = ClassUtil.getClassLoader().getResources(BASE_PACKAGE.replace(".", "/"));
            
         // 遍历 URL 资源
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if(url == null ) continue;
                    
                String protocol = url.getProtocol();
                
                if (protocol.equals("file")) {
                    // 若在 class 目录中，则执行添加类操作
                    String packagePath = url.getPath().replaceAll("%20", " ");
                    cacheAnnotationClazz(packagePath, BASE_PACKAGE);
                } else if (protocol.equals("jar") && DEEP_SCAN) {
                    
                    // 若在 jar 包中，则解析 jar 包中的 entry
                    JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                    JarFile jarFile = jarURLConnection.getJarFile();
                    Enumeration<JarEntry> jarEntries = jarFile.entries();
                    while (jarEntries.hasMoreElements()) {
                        JarEntry jarEntry = jarEntries.nextElement();
                        String jarEntryName = jarEntry.getName();
                        // 判断该 entry 是否为 class
                        if (jarEntryName.endsWith(".class")) {
                            // 获取类名
                            String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                            // 执行添加类操作
                            doCacheAnnotationClazz(className);
                        }
                    }
                }
                
            }
            
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        
    }
    
    
    private static void cacheAnnotationClazz(String packagePath,String packageName){
        try {
            // 获取包名路径下的 class 文件或目录
            File[] files = new File(packagePath).listFiles(new FileFilter() {
                @Override
                public boolean accept(File file) {
                    return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
                }
            });
            // 遍历文件或目录
            for (File file : files) {
                String fileName = file.getName();
                // 判断是否为文件或目录
                if (file.isFile()) {
                    // 获取类名
                    String className = fileName.substring(0, fileName.lastIndexOf("."));
                    if (StringUtils.isNotEmpty(packageName)) {
                        className = packageName + "." + className;
                    }
                    // 执行添加类操作
                    doCacheAnnotationClazz(className);
                } else {
                    // 获取子包
                    String subPackagePath = fileName;
                    if (StringUtils.isNotEmpty(packagePath)) {
                        subPackagePath = packagePath + "/" + subPackagePath;
                    }
                    // 子包名
                    String subPackageName = fileName;
                    if (StringUtils.isNotEmpty(packageName)) {
                        subPackageName = packageName + "." + subPackageName;
                    }
                    // 递归调用
                    cacheAnnotationClazz(subPackagePath, subPackageName);
                }
            }
        } catch (Exception e) {
            logger.error("添加类出错！", e);
        }
    }
    
    private static void doCacheAnnotationClazz(String className){
        
        Class<?> cls = ClassUtil.loadClass(className, false);
        // 判断是否可以添加类
        Annotation[] annotations = cls.getAnnotations();
        
        if(annotations == null || annotations.length == 0){
            return ;
        }
        
        for (Annotation a : annotations){
            String annotationName = a.annotationType().getName();
            
            //防止过度
            if(!annotationName.startsWith(BASE_PACKAGE)){
               continue; 
            }
            
            List<Class<?>> annotationClazzList = annotationClazzMap.get(annotationName);
            if(annotationClazzList == null){
                annotationClazzList = new ArrayList<>();
                annotationClazzMap.put(annotationName, annotationClazzList);
            }
            
            annotationClazzList.add(cls);
            logger.debug("cache [{}] for [{}].",cls.getName(),annotationName);
        }
    }

    /**
     * 获取类加载器
     */
    public static ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 获取类路径
     */
    public static String getClassPath() {
        String classpath = "";
        URL resource = getClassLoader().getResource("");
        if (resource != null) {
            classpath = resource.getPath();
        }
        return classpath.substring(1, classpath.length());
    }

    /**
     * 加载类（将自动初始化）
     */
    public static Class<?> loadClass(String className) {
        return loadClass(className, true);
    }

    /**
     * 加载类
     */
    public static Class<?> loadClass(String className, boolean isInitialized) {
        Class<?> cls;
        try {
            cls = Class.forName(className, isInitialized, getClassLoader());
        } catch (ClassNotFoundException e) {
            logger.error("加载类出错！", e);
            throw new RuntimeException(e);
        }
        return cls;
    }
    
    public static List<Class<?>> getAnnotationClassList(Class<? extends Annotation> annotationClass){
        
        if(annotationClazzMap.isEmpty()){
            initAnnotationClazzCache();
        }
        
        return annotationClazzMap.get(annotationClass.getName());
    }
    
    
    /**
     * 获取指定注解的类
     * @param packageName
     * @param annotationClass
     * @param scanJar
     * @return
     */
    public static List<Class<?>> getAnnotationClassList(String packageName,Class<? extends Annotation> annotationClass,boolean scanJar){
        
        List<Class<?>> classList = new ArrayList<Class<?>>();
        
        try {
            // 从包名获取 URL 类型的资源
            Enumeration<URL> urls = ClassUtil.getClassLoader().getResources(packageName.replace(".", "/"));
            // 遍历 URL 资源
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    // 获取协议名（分为 file 与 jar）
                    
                    logger.debug("urls:{}",url.getPath());
                    String protocol = url.getProtocol();
                    if (protocol.equals("file")) {
                        // 若在 class 目录中，则执行添加类操作
                        String packagePath = url.getPath().replaceAll("%20", " ");
                        addClass(classList, packagePath, packageName,annotationClass);
                    } else if (protocol.equals("jar") && scanJar) {
                        
                        // 若在 jar 包中，则解析 jar 包中的 entry
                        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                        JarFile jarFile = jarURLConnection.getJarFile();
                        Enumeration<JarEntry> jarEntries = jarFile.entries();
                        while (jarEntries.hasMoreElements()) {
                            JarEntry jarEntry = jarEntries.nextElement();
                            String jarEntryName = jarEntry.getName();
                            // 判断该 entry 是否为 class
                            if (jarEntryName.endsWith(".class")) {
                                // 获取类名
                                String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                                // 执行添加类操作
                                doAddClass(classList, className,annotationClass);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("获取类出错！", e);
        }
        
        return classList;
    }
    
    private static void addClass(List<Class<?>> classList, String packagePath, String packageName,Class<? extends Annotation> annotationClass) {
        try {
            // 获取包名路径下的 class 文件或目录
            File[] files = new File(packagePath).listFiles(new FileFilter() {
                @Override
                public boolean accept(File file) {
                    return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
                }
            });
            // 遍历文件或目录
            for (File file : files) {
                String fileName = file.getName();
                // 判断是否为文件或目录
                if (file.isFile()) {
                    // 获取类名
                    String className = fileName.substring(0, fileName.lastIndexOf("."));
                    if (StringUtils.isNotEmpty(packageName)) {
                        className = packageName + "." + className;
                    }
                    // 执行添加类操作
                    doAddClass(classList, className,annotationClass);
                } else {
                    // 获取子包
                    String subPackagePath = fileName;
                    if (StringUtils.isNotEmpty(packagePath)) {
                        subPackagePath = packagePath + "/" + subPackagePath;
                    }
                    // 子包名
                    String subPackageName = fileName;
                    if (StringUtils.isNotEmpty(packageName)) {
                        subPackageName = packageName + "." + subPackageName;
                    }
                    // 递归调用
                    addClass(classList, subPackagePath, subPackageName,annotationClass);
                }
            }
        } catch (Exception e) {
            logger.error("添加类出错！", e);
        }
    }
    
    private static void doAddClass(List<Class<?>> classList, String className,Class<? extends Annotation> annotationClass) {
        // 加载类
        logger.debug("trying {}",className);
        Class<?> cls = ClassUtil.loadClass(className, false);
        // 判断是否可以添加类
        if (cls.isAnnotationPresent(annotationClass)) {
            // 添加类
            classList.add(cls);
        }
    }
    
    public static void main(String[] args) {
        
        initAnnotationClazzCache();    
        
        for(Map.Entry<String,List<Class<?>>>  entry : annotationClazzMap.entrySet()){
            System.out.println("===========" + entry.getKey() + "==============");
            for (int i = 0; i < entry.getValue().size(); i++) {
                System.out.println(entry.getValue().get(i).getName());
            }
        }
        
    }
}
